<template>
  <div class="storyboard">
    <toast-message ref="toast" />
    <div class="container">
      <div class="storyboard-header">
        <div class="header-actions">
          <el-button
            @click="characterDrawerVisible = true"
            type="primary"
          >
            角色配置
          </el-button>
          <el-button
            @click="configDrawerVisible = true"
            type="primary"
          >
            打开配置
          </el-button>
        </div>
      </div>
      <el-divider />
      <div class="storyboard-content">
        <storyboard-panel
          :generating="generating"
          @generate="handleGenerate"
          @update:shots="shots = $event"
          class="storyboard-main"
        />

        <!-- 添加分镜列表抽屉按钮 -->
        <div class="storyboard-drawer-toggle">
          <el-button
            type="primary"
            @click="imagePanelDrawerVisible = true"
            :icon="ArrowRight"
            circle
          />
        </div>
      </div>
    </div>
  </div>

  <el-drawer
    v-model="configDrawerVisible"
    title="SD配置"
    size="50%"
    direction="rtl"
  >
    <SDPanel
      :messages="[]"
      :generating="false"
      :sd-service-available="sdServiceAvailable"
    />
  </el-drawer>

  <el-drawer
    v-model="characterDrawerVisible"
    title="角色配置"
    size="60%"
    direction="rtl"
  >
    <CharacterManager />
  </el-drawer>

  <!-- 分镜列表抽屉 -->
  <el-drawer
    v-model="imagePanelDrawerVisible"
    title="分镜列表"
    size="85%"
    direction="rtl"
  >
    <div class="storyboard-sidebar-container">
      <storyboard-image-panel
        :shots="paginatedShots"
        :sd-service-available="sdServiceAvailable"
        :total-shots="totalShots"
        :current-page="currentPage"
        :page-size="pageSize"
        @generate-image="handleGenerateImage"
        @generate-variants="handleGenerateVariants"
        @regenerate-prompt="handleRegeneratePrompt"
        @update:shots="updatePaginatedShots"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
        class="storyboard-sidebar"
      />
    </div>
  </el-drawer>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue'
import StoryboardPanel from '@/components/StoryboardPanel.vue'
import StoryboardImagePanel from '@/components/StoryboardImagePanel.vue'
import SDPanel from '@/components/SDPanel.vue'
import CharacterManager from '@/components/CharacterManager.vue'
import { sdApi } from '@/services/sd'
import ToastMessage from '@/components/ToastMessage.vue'
import { ArrowRight } from '@element-plus/icons-vue'

const toast = ref<InstanceType<typeof ToastMessage> | null>(null)

const parseAIResponse = (content: string): Shot[] => {
  const parsedShots: Shot[] = []

  try {
    // 首先尝试处理整个内容作为一个整体
    // 预处理：合并可能被分割的行
    let processedContent = content
      .replace(/\n\nAI分镜词/g, '\nAI分镜词') // 处理分镜词前的多余换行
      .replace(/镜头(\d+)[：:]/g, '镜头$1:') // 统一冒号格式
      .replace(/\*\*/g, '') // 移除星号标记

    // 按行处理，支持跨多行的数据
    const allLines = processedContent.split('\n')
    let currentShot: Partial<Shot> = {
      // 初始化可选属性，确保它们有默认值
      generating: false,
      generatingVariants: false,
      regeneratingPrompt: false
    }

    for (let i = 0; i < allLines.length; i++) {
      const line = allLines[i].trim()
      if (!line) continue

      // 检测新的镜头开始
      if (line.match(/镜头\d+[：:]/)) {
        // 如果已经有一个完整的镜头，保存它
        if (currentShot.originalText && currentShot.englishPrompt) {
          parsedShots.push(currentShot as Shot)
        }

        // 开始新的镜头
        currentShot = {
          generating: false,
          generatingVariants: false,
          regeneratingPrompt: false
        }

        // 提取镜头文本
        const match = line.match(/镜头\d+[：:](.*)/) || []
        if (match[1]) {
          // 移除可能存在的星号标记
          currentShot.originalText = match[1].trim().replace(/\*\*/g, '')
        } else {
          // 如果当前行只有镜头号，尝试从下一行获取文本
          if (i + 1 < allLines.length && !allLines[i + 1].includes('AI分镜词')) {
            // 移除可能存在的星号标记
            currentShot.originalText = allLines[i + 1].trim().replace(/\*\*/g, '')
            i++ // 跳过下一行，因为已经处理了
          }
        }
      }
      // 处理AI分镜词
      else if (line.includes('AI分镜词')) {
        const colonIndex = line.indexOf(':')
        if (colonIndex !== -1) {
          // 分镜词在同一行
          currentShot.englishPrompt = line.slice(colonIndex + 1).trim()
        } else {
          // 分镜词可能在下一行
          let promptText = ''
          let j = i + 1
          // 收集所有属于当前分镜词的行，直到下一个镜头或结束
          while (j < allLines.length && !allLines[j].match(/镜头\d+[：:]/)) {
            promptText += allLines[j].trim() + ' '
            j++
          }
          currentShot.englishPrompt = promptText.trim()
          i = j - 1 // 更新索引
        }
      }
      // 如果不是镜头开始也不是AI分镜词，可能是镜头描述的一部分
      else if (currentShot && !currentShot.originalText && !line.match(/AI分镜词/)) {
        // 移除可能存在的星号标记
        currentShot.originalText = line.replace(/\*\*/g, '')
      }
      // 如果当前行不包含关键词但前面有镜头号，可能是镜头描述的继续
      else if (currentShot && currentShot.originalText && !currentShot.englishPrompt) {
        // 如果不是AI分镜词的开始，则认为是镜头描述的一部分
        if (!line.match(/AI分镜词/)) {
          // 移除可能存在的星号标记后再拼接
          currentShot.originalText += ' ' + line.replace(/\*\*/g, '')
        }
      }
    }

    // 添加最后一个镜头（如果有）
    if (currentShot.originalText && currentShot.englishPrompt) {
      parsedShots.push(currentShot as Shot)
    }

    // 如果上面的方法没有解析出任何镜头，尝试备用解析方法
    if (parsedShots.length === 0) {
      // 尝试匹配整个内容中的镜头和分镜词
      const shotMatch = content.match(/镜头\d+[：:](([\s\S]*?)(?=\nAI分镜词|$))/)
      const promptMatch = content.match(/AI分镜词[：:](([\s\S]*?)(?=\n镜头\d+|$))/)

      if (shotMatch && promptMatch) {
        currentShot = {
          // 移除可能存在的星号标记
          originalText: shotMatch[1].trim().replace(/\*\*/g, ''),
          englishPrompt: promptMatch[1].trim(),
          generating: false,
          generatingVariants: false,
          regeneratingPrompt: false
        }
        parsedShots.push(currentShot as Shot)
      }
    }
  } catch (error) {
    console.error('解析AI响应失败:', error)
    // 即使解析失败，也返回已解析的内容
  }

  return parsedShots
}

// 使用统一的Shot类型定义
import { Shot } from '@/types'

const shots = ref<Shot[]>([])
const generating = ref(false)
const sdServiceAvailable = ref(false)
const firstDataReceived = ref(false)
const configDrawerVisible = ref(false)
const characterDrawerVisible = ref(false)
const imagePanelDrawerVisible = ref(false)

// 构建分镜生成的提示词
let prompt = `[PROMPT BEGIN]
      您将作为一位专业的AI绘图分镜师，您的核心任务是将我提供的小说文本段落转化为适合Stable Diffusion、Midjourney等AI绘图工具使用的详细分镜脚本。您需要严格按照以下规则进行文本切割、关键词生成及格式输出。
      生成规则：
      镜头切割策略：
      对话独立： 每出现一段用引号括起来的独立对话，无论说话人是谁，都强制切割为一个新的分镜。
      场景/状态转换： 当原文描述发生明显的场景切换、时间推移、人物主要状态变化（如情绪从平静到激动）、或使用了“突然”、“这时”、“然而”、“随即”等转折词语暗示画面应立即切换时，强制切割为一个新的分镜。
      连续动作/描述组： 分析原文中的连续描写或动作组。以逗号将原文分割成语义完整的小片段。每个小片段（通常包含1到2个主要动词或核心描述）作为一个分镜。如果一个句子包含多个独立或连续性较强的动作/描述（如超过3个），则在自然的停顿点（如逗号、连词后）进行切割，将其分配到不同的分镜中。
      AI分镜词生成原则：
      对于每个分镜，分析其对应的原文片段。
      生成详细、具象化的AI绘图关键词。关键词应涵盖画面中的所有主要视觉元素。
      关键词必须严格按照以下顺序排列：
      masterpiece, best quality, high resolution, intricate details (强制包含，位于最前)
      主体描述： 根据原文侧重，详细描述主要人物（年龄、性别、外貌、衣着、表情、情绪、姿态）或核心场景（建筑类型、地点类型、天气、时间）。优先描述原文最侧重的主体。
      主要动作/状态： 描述人物正在进行的具体动作（standing, running, fighting）或场景的关键状态（raining, sunlight streaming）。
      环境/背景细节： 补充描述场景的其他元素（trees, furniture, city street, forest, water）。
      光线/氛围： 描述画面中的光线类型（sunlight, moonlight, indoor light）、阴影（shadows, harsh shadows）、色彩基调（warm tone, cold tone）以及整体氛围（peaceful, tense, chaotic）。
      构图/视角： 描述画面的景别（full body, medium shot, close up, wide shot）、视角（from above, low angle, profile view, looking at viewer）以及镜头效果（motion blur, depth of field）。
      其他细节/道具： 补充描述画面中的特定道具或微小但重要的视觉元素。
      关键词之间使用半角逗号 , 进行分隔。
      关键词列表的末尾不允许出现任何标点符号。
      强制输出格式：
      镜头1:原文片段
      AI分镜词:masterpiece, best quality, [主体描述], [角色描述], [镜头类型], [风格强化]
      请注意： 在开始生成第一个分镜脚本之前，或在所有分镜生成完毕之后，不要输出任何其他引言、解释、总结或结束语。直接开始第一个镜头的格式输出。
      [PROMPT END]`


// 分页相关状态
const currentPage = ref(1)
const pageSize = ref(10)
const totalShots = computed(() => shots.value.length)
const totalPages = computed(() => Math.ceil(totalShots.value / pageSize.value))

// 当前页的分镜数据
const paginatedShots = computed(() => {
  const startIndex = (currentPage.value - 1) * pageSize.value
  const endIndex = startIndex + pageSize.value
  return shots.value.slice(startIndex, endIndex)
})

// 定期检查SD服务状态
const checkSDService = async () => {
  try {
    const response = await sdApi.checkStatus()
    sdServiceAvailable.value = response?.data?.available ?? false
  } catch (error) {
    toast.value?.show('检查SD服务状态失败', 3000)
    sdServiceAvailable.value = false
  }
}

// 初始化时检查SD服务状态并启动定时检查
onMounted(() => {
  checkSDService()
  // 每30秒检查一次服务状态
  //const timer = setInterval(checkSDService, 30000)

  // 组件卸载时清除定时器
  onUnmounted(() => {
    //clearInterval(timer)
  })
})

// const toPath = (path: string) => {
//   router.push(path)
// }

const handleGenerate = async (text: string, model: string, isSmartMode: boolean) => {
  if (!text.trim()) {
    toast.value?.show('请输入场景描述', 3000)
    return
  }
  generating.value = true
  firstDataReceived.value = false
  shots.value = []
  try {
    // 手动分镜模式：直接解析文本生成分镜列表
    if (!isSmartMode) {
      // 直接解析用户输入的文本
      const shotData = parseText(text)
      if (shotData.length > 0) {
        // 更新分镜列表
        shots.value = shotData
        firstDataReceived.value = true
      } else {
        toast.value?.show('解析文本失败，请检查文本格式是否正确', 3000)
        generating.value = false
      }
    } else {
      // 智能分镜模式：调用API
      prompt += text
      // 发送 POST 请求
      const response = await fetch('/api/chat_storyboard', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          message: prompt,
          model: model
        })
      })

      if (!response.ok || !response.body) {
        toast.value?.show('请求失败', 3000)
      }
      // 读取流数据
      const reader = response.body.getReader()
      const decoder = new TextDecoder()

      let buffer = '' // 用于存储未完整解析的流数据
      let currentContent = ''

      while (true) {
        const { done, value } = await reader.read()
        if (done) break

        buffer += decoder.decode(value, { stream: true })

        // 拆分 SSE 数据
        const parts = buffer.split('\n\n')

        for (let i = 0; i < parts.length - 1; i++) {
          const line = parts[i].trim()
          if (line.startsWith('data: ')) {
            try {
              const jsonStr = line.substring(6).trim() // 去掉 `data: ` 前缀
              const jsonData = JSON.parse(jsonStr) // 解析 JSON
              if (jsonData.error) {
                toast.value?.show(jsonData.error, 3000)
              }

              if (!jsonData.done) {
                // 直接显示完整内容
                currentContent += jsonData.content
                // 解析当前累积的内容
                const shotData = parseAIResponse(currentContent)
                if (shotData.length > 0) {
                  // 更新分镜列表
                  shots.value = shotData
                  // 在收到第一个有效数据时移除全局loading状态
                  if (!firstDataReceived.value) {
                    firstDataReceived.value = true
                  }
                }
              } else {
                break
              }
            } catch (e) {
              console.error('解析响应数据失败:', e)
              toast.value?.show('解析响应数据失败', 3000)
            }
          }
        }
        // 只保留最后一个未完整的部分
        buffer = parts[parts.length - 1]
      }
    }
  } catch (error) {
    console.error('生成分镜失败:', error)
  } finally {
    generating.value = false
    // 如果成功生成了分镜，自动打开分镜列表抽屉
    if (shots.value.length > 0) {
      imagePanelDrawerVisible.value = true
    }
  }
}

const handleGenerateImage = async (shot: Shot) => {
  if (!sdServiceAvailable.value) return
  // 找到当前shot在数组中的索引，用于更新状态
  const index = shots.value.findIndex((s) => s.originalText === shot.originalText)
  if (index === -1) return
  // 设置loading状态
  shots.value[index] = { ...shot, generating: true }
  shots.value = [...shots.value] // 触发视图更新

  let params = {
    prompt: shot.finalPrompt,
    negative_prompt:
      'nsfw,(mutated hands and fingers:1.4),(worst quality:1.4),(low quality:1.4),(normal quality:1.4),bad anatomy,wrong anatomy,extra limb,missing limb,floating limbs,disconnected limbs,mutation,mutated,ugly,disgusting,blurry,amputation,tattoo,cropped,backlight,nsfw,'
  }
  try {
    const response = await sdApi.generate(params)

    if (response?.data?.image) {
      // 单张图片处理（兼容旧版本）
      shots.value[index] = { ...shots.value[index], image: response.data.image, generating: false }
      shots.value = [...shots.value] // 触发视图更新
    } else if (response?.data?.images && response.data.images.length > 0) {
      // 处理批量生成的多张图片
      const images = response.data.images

      // 确保variantImages数组已初始化
      if (!shots.value[index].variantImages) {
        shots.value[index].variantImages = []
      }

      // 第一张图片设为主图片
      shots.value[index].image = images[0]

      // 其余图片添加到备选图片数组
      if (images.length > 1) {
        for (let i = 1; i < images.length; i++) {
          shots.value[index].variantImages.push(images[i])
        }
      }

      shots.value[index].generating = false
      shots.value = [...shots.value] // 触发视图更新
    } else if (response?.data?.error) {
      // 处理API返回的错误
      console.error('SD API返回错误:', response.data.error)
      toast.value?.show(`生成图片失败: ${response.data.error}`, 3000)
      shots.value[index] = { ...shots.value[index], generating: false }
      shots.value = [...shots.value] // 触发视图更新
    } else {
      throw new Error('生成图片失败：未收到有效响应')
    }
  } catch (error) {
    console.error('生成图片失败:', error)
    // 显示错误提示
    toast.value?.show('生成图片失败，请检查网络或服务状态', 3000)
    // 重置loading状态
    shots.value[index] = { ...shots.value[index], generating: false }
    shots.value = [...shots.value] // 触发视图更新
  }
}

const handleGenerateVariants = async (shot: Shot) => {
  if (!sdServiceAvailable.value || !shot.image) return

  // 找到当前shot在数组中的索引，用于更新状态
  const index = shots.value.findIndex((s) => s === shot)
  if (index === -1) return

  // 设置loading状态
  const updatedShot = { ...shot, generatingVariants: true }
  shots.value[index] = updatedShot
  shots.value = [...shots.value] // 触发视图更新

  try {
    const response = await sdApi.generate({
      prompt: shot.englishPrompt,
      negative_prompt:
        'nsfw, lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry'
    })
    if (response.data?.images?.[0]) {
      shots.value[index].image = response.data.images[0]
    } else if (response.data?.error) {
      // 处理API返回的错误
      console.error('SD API返回错误:', response.data.error)
      toast.value?.show(`生成备选图片失败: ${response.data.error}`, 3000)
    }
  } catch (error) {
    console.error('生成备选图片失败:', error)
    // 显示错误提示
    toast.value?.show('生成备选图片失败，请检查网络或服务状态', 3000)
  } finally {
    // 无论成功还是失败，都重置loading状态
    if (index !== -1 && index < shots.value.length) {
      shots.value[index].generatingVariants = false
      shots.value = [...shots.value] // 触发视图更新
    }
  }
}

const handleRegeneratePrompt = async (shot: Shot) => {
  // 找到当前shot在数组中的索引
  const index = shots.value.findIndex((s) => s.originalText === shot.originalText)
  if (index === -1) return

  try {
    // 设置loading状态
    const updatedShot = { ...shot, regeneratingPrompt: true }
    shots.value[index] = updatedShot
    shots.value = [...shots.value] // 触发视图更新

    let prompt = `[PROMPT BEGIN]
            你是专业的静态画面分镜生成器，请将我提供的小说文本转换为适用于 Stable Diffusion 图像生成的英文提示词,输出格式：
            AI分镜词: masterpiece, best quality, [character tags], [pose], [scene], [lighting], [view angle], [style keywords]
            输出限制：
            只输出英文提示词
            禁止任何解释、非分镜内容、系统提示、注释、中文补充
            [PROMPT END]`
    // 构建分镜生成的提示词
    prompt += prompt + shot.originalText
    // 发送 POST 请求
    const response = await fetch('/api/chat_storyboard', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        message: prompt
      })
    })

    if (!response.ok || !response.body) {
      throw new Error('请求失败')
    }

    // 读取流数据
    const reader = response.body.getReader()
    const decoder = new TextDecoder()

    let buffer = '' // 用于存储未完整解析的流数据
    let currentContent = ''

    while (true) {
      const { done, value } = await reader.read()
      if (done) break

      buffer += decoder.decode(value, { stream: true })

      // 拆分 SSE 数据
      const parts = buffer.split('\n\n')

      for (let i = 0; i < parts.length - 1; i++) {
        const line = parts[i].trim()
        if (line.startsWith('data: ')) {
          try {
            const jsonStr = line.substring(6).trim() // 去掉 `data: ` 前缀
            const jsonData = JSON.parse(jsonStr) // 解析 JSON
            if (jsonData.error) {
              throw new Error(jsonData.error)
            }

            if (!jsonData.done) {
              // 直接显示完整内容
              currentContent += jsonData.content
              // 解析当前累积的内容，提取AI分镜词
              const lines = currentContent.split('\n')
              for (const line of lines) {
                if (line.startsWith('AI分镜词:')) {
                  const newPrompt = line.slice(6).trim()
                  if (newPrompt) {
                    // 更新描述词，清除图片（因为描述词已变更）
                    const updatedShot = { ...shot, englishPrompt: newPrompt, image: undefined }
                    // 更新数组中的对象
                    shots.value[index] = updatedShot
                    // 触发数组更新
                    shots.value = [...shots.value]
                    break
                  }
                }
              }
            } else {
              break
            }
          } catch (e) {
            console.error('解析响应数据失败:', e)
            throw e
          }
        }
      }

      // 只保留最后一个未完整的部分
      buffer = parts[parts.length - 1]
    }
  } catch (error) {
    console.error('重新生成描述词失败:', error)
    toast.value?.show('重新生成描述词失败，请稍后重试', 3000)
  } finally {
    // 无论成功还是失败，都重置loading状态
    if (index !== -1 && index < shots.value.length) {
      shots.value[index].regeneratingPrompt = false
      shots.value = [...shots.value] // 触发视图更新
    }
  }
}
// 更新分页数据的方法
const updatePaginatedShots = (updatedShots: Shot[]) => {
  // 计算当前页的起始索引
  const startIndex = (currentPage.value - 1) * pageSize.value

  // 创建shots的副本
  const newShots = [...shots.value]

  // 检查是否有分镜被合并（数量减少）
  if (updatedShots.length < Math.min(pageSize.value, newShots.length - startIndex)) {
    // 从全局数组中移除被合并的分镜
    // 先替换当前页的分镜
    for (let i = 0; i < updatedShots.length; i++) {
      const globalIndex = startIndex + i
      if (globalIndex < newShots.length) {
        newShots[globalIndex] = updatedShots[i]
      }
    }
    // 计算需要移除的分镜数量
    const removedCount = Math.min(pageSize.value, newShots.length - startIndex) - updatedShots.length
    // 从数组中移除多余的分镜
    if (removedCount > 0) {
      newShots.splice(startIndex + updatedShots.length, removedCount)
    }
  } else {
    // 正常更新当前页的数据
    for (let i = 0; i < updatedShots.length; i++) {
      const globalIndex = startIndex + i
      if (globalIndex < newShots.length) {
        newShots[globalIndex] = updatedShots[i]
      }
    }
  }
  // 更新整个shots数组
  shots.value = newShots
}

// 处理页码变化
const handleCurrentChange = (page: number) => {
  currentPage.value = page
}

// 处理每页显示数量变化
const handleSizeChange = (size: number) => {
  pageSize.value = size
  // 当页面大小变化时，确保当前页码有效
  if (currentPage.value > totalPages.value) {
    currentPage.value = totalPages.value || 1
  }
}

function parseText(text: string): Shot[] {
  const shots: Shot[] = []

  // 1. 先按“镜头”分割每一段
  const scenes = text.split(/(?=镜头\d+[:：])/g) // 注意保留分隔词

  for (const scene of scenes) {
    const match = scene.match(/镜头\d+[:：]\s*(.*?)\s*AI分镜词[:：]\s*([\s\S]*)/)

    if (match) {
      const description = match[1].trim()
      const aiWordsRaw = match[2].trim().replace(/\s*,\s*/g, ', ') // 标准化逗号空格

      const aiWords = aiWordsRaw.split(',')
        .map(word => word.trim())
        .filter(word => word.length > 0)

      shots.push({
        originalText: description,
        englishPrompt: aiWords.join(', ')
      })
    }
  }

  return shots
}


</script>

<style scoped>
.storyboard {
  height: 100%;
  display: flex;
  flex-direction: column;
  position: relative;
}

.container {
  flex: 1;
  padding: 20px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  position: absolute;
  width: 30%;
}

.storyboard-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
}

.header-actions {
  display: flex;
  gap: 10px;
}

.storyboard-content {
  display: flex;
  flex: 1;
  overflow: hidden;
  position: relative;
  gap: 1rem;
  min-width: 0;
}

.storyboard-main {
  flex: 1;
  overflow-y: auto;
}

.storyboard-drawer-toggle {
  position: fixed;
  right: 5rem;
  top: 50%;
  transform: translateY(-50%);
  z-index: 10;
}

.storyboard-sidebar-container {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  flex: 1;
  min-width: 0;
}

.storyboard-sidebar {
  flex: 1;
  overflow-y: auto;
  padding: 0 20px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.pagination-container {
  margin-top: 20px;
  display: flex;
  justify-content: center;
  padding-bottom: 20px;
}
</style>
